iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0

前言

之前我們提到任務的 created_at 與 updated_at 兩個欄位因為產生欄位值的時間點有一點點誤差,導致兩個時間不一致的問題。今天讓我們用客製化欄位的方式來解決這個問題吧!

客製化欄位

建立 CreatedAt 欄位

讓我們先建立 server/utils/models.py 檔案

from django.db import models


class CreatedAtField(models.DateTimeField):
    def __init__(self, *args, **kwargs) -> None:
        kwargs["auto_now_add"] = True
        super().__init__(*args, **kwargs)

這邊做的事情是建立了一個客製化欄位名為 CreatedAtField 他繼承了 DateTimeField,唯一的不同是他會自動地將 auto_now_add 設定為 True 這樣當使用這個欄位時就不用手動修改了,現在讓我們打開 server/app/todo/models.py 檔案

from django.db import models

+from server.utils import models as model_utils
+

class Tag(models.Model):
    name = models.CharField(max_length=255, unique=True)
    description = models.TextField(blank=True)

    def __str__(self):
        return self.name

# ...... 中間省略 ......

class Task(models.Model):
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    is_finish = models.BooleanField(default=False)
    tags = models.ManyToManyField(Tag)
    end_at = models.DateTimeField(null=True, blank=True)
-   created_at = models.DateTimeField(auto_now_add=True)
+   created_at = model_utils.CreatedAtField()
    updated_at = models.DateTimeField(auto_now=True)
    category = models.ForeignKey(Category, on_delete=models.PROTECT)

    def __str__(self):
        return self.title

這邊我們將任務的 created_at 欄位替換成我們剛剛建立的欄位,接著讓我們產生一下遷移檔,並套用到資料庫(記得啟動虛擬環境)

python manage.py makemigrations
python manage.py migrate

現在大家可以使用 POST 方法請求 http://127.0.0.1:8000/api/todo/tasks(記得啟動 server 唷)

大家可以發現效果還是跟之前一樣,這是正常的因為我們目前還沒有調整最後編輯時間。

建立 UpdatedAt 欄位

接著我們編輯 server/utils/models.py 檔案(把下方內容貼在最下面)

class UpdatedAtField(models.DateTimeField):
    def __init__(self, *args, **kwargs) -> None:
        kwargs["auto_now"] = True
        super().__init__(*args, **kwargs)

    def pre_save(self, model_instance, add):
        if add:
            for field in model_instance._meta.get_fields():
                if not isinstance(field, CreatedAtField):
                    continue

                value = getattr(model_instance, field.name)
                setattr(model_instance, self.attname, value)
                return value

        return super().pre_save(model_instance, add)

這邊我們做的事情是建立一個 UpdatedAtField 他繼承了 DateTimeField,只是他會自動地將 auto_now 設定為 True,同時他做資料儲存時他會先判斷目前是建立還是更新(透過 pre_save 的 add 參數),如果是更新或是 Model 中沒有 CreatedAtField 的話呼叫 DateTimeField 的 pre_save,但如果是新增且有 CreatedAtField 的話,就使用 CreatedAtField 產生的時間當作當前欄位的值,這樣 created_at 與 updated_at 的時間就會相同了。

現在讓我們編輯 server/app/todo/models.py 檔案

# ...... 以上省略 ......

class Task(models.Model):
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    is_finish = models.BooleanField(default=False)
    tags = models.ManyToManyField(Tag)
    end_at = models.DateTimeField(null=True, blank=True)
    created_at = model_utils.CreatedAtField()
-   updated_at = models.DateTimeField(auto_now=True)
+   updated_at = model_utils.UpdatedAtField()
    category = models.ForeignKey(Category, on_delete=models.PROTECT)

    def __str__(self):
        return self.title

這邊我們將任務的 updated_at 欄位替換成我們剛剛建立的欄位,接著讓我們產生一下遷移檔,並套用到資料庫(記得啟動虛擬環境)

python manage.py makemigrations
python manage.py migrate

現在大家可以使用 POST 方法請求 http://127.0.0.1:8000/api/todo/tasks 可以發現 created_at 與 updated_at 兩個欄位的值是一樣的了,但是在編輯時還是只會更新 updated_at 的值。

總結

今天這樣修改後,我們就可以讓 created_at 與 updated_at 這兩個欄位的值在建立資料時是一樣的。如果大家有需要重複利用的欄位邏輯,或是希望基於某個欄位修改他的行為,就可以跟我們今天一樣繼承原本的欄位來修改,如果想知道怎麼修改可以看看這篇文件

結束前別忘了檢查一下今天的程式碼有沒有問題,並排版好喔。

ruff check --fix .
black .
pyright .

今天的內容就到這邊了,讓我們期待明天的內容吧。

P.S. 今天的檔案更新可以參考我的 Git Commit 大家可以搭配服用


上一篇
Day25 - Query N+1 問題
下一篇
Day27 - 檔案上傳
系列文
Django REST 大冒險:探索精彩紛呈的 API 開發世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言